package entities;

import game.Level;
import game.LevelBuilder;
import game.SpecuspaceException;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Random;

import javax.swing.event.EventListenerList;

import listeners.SpawnEvent;
import listeners.SpawnListener;
import util.Vector2D;

import components.BasicCollisionModel;
import components.CenterPointCollisionModel;
import components.CollisionModel;
import components.EntityComponent;
import components.EntityView;
import components.Vectorial;
import components.ViewBasedCollisionModel;

/**
 * <p>
 * This class is the base class for all object on the screen (excluding HUD,
 * menus etc.).<br>
 * Subclasses are created by a {@link LevelBuilder} via getInstance() and can
 * therefore not have a parameterized constructor.<br>
 * When created, the method {@link #init(Properties)} should be called.
 * </p>
 * 
 * @author Sebastian
 * 
 */
public abstract class Entity implements Vectorial {

	protected static Random random = new Random();

	/**
	 * This entitiy's center position
	 */
	protected Vector2D pos;

	/**
	 * The current level, instantiated via {@link #spawn(Level, Vector2D)}
	 */
	protected Level level;

	/**
	 * Components in this list cannot be null
	 */
	protected List<EntityComponent> components;

	/**
	 * These components can be null, but will not be updated if so
	 */
	protected EntityView view;
	protected CollisionModel collisionModel;

	protected EventListenerList listeners;

	public Entity() {
		components = new ArrayList<EntityComponent>();
		listeners = new EventListenerList();
		view = null;
		collisionModel = null;
	}

	public Entity(CollisionModel model, EntityView view) {
		this();
		this.collisionModel = model;
		this.view = view;
	}

	public void tick(float dt) {
		for (EntityComponent c : components) {
			c.tick(dt);
		}
	}

	public void spawn(Level level, Vector2D pos) {
		if (level != null) {
			this.level = level;
			setPosition(pos);
			level.addEntity(this);
			SpawnEvent e = new SpawnEvent(this, this, level);
			for (SpawnListener sl : listeners.getListeners(SpawnListener.class)) {
				sl.entitySpawned(e);
			}
		}
	}

	public void spawn(Level level) {
		spawn(level, pos);
	}

	public void unspawn() {
		if (level != null) {
			SpawnEvent e = new SpawnEvent(this, this, level);
			for (SpawnListener dl : listeners.getListeners(SpawnListener.class)) {
				dl.entityUnspawned(e);
			}
			level.removeEntity(this);
		}
	}

	public boolean collidesWith(Entity e) {
		return collisionModel != null && e.getCollisionModel() != null
				&& collisionModel.collidesWith(e.getCollisionModel());
	}

	public void addComponent(EntityComponent component) {
		if (component != null) {
			components.add(component);
		}
	}

	public void removeComponent(EntityComponent component) {
		if (components.contains(component)) {
			components.remove(component);
		}
	}

	public void addSpawnListener(SpawnListener l) {
		listeners.add(SpawnListener.class, l);
	}

	public void removeSpawnListener(SpawnListener l) {
		listeners.remove(SpawnListener.class, l);
	}

	public Vector2D getPosition() {
		return pos;
	}

	public void setPosition(Vector2D pos) {
		this.pos = pos;
	}

	public float getX() {
		return pos.getX();
	}

	public float getY() {
		return pos.getY();
	}

	@Override
	public Vector2D getVector() {
		return getPosition();
	}

	@Override
	public void setVector(Vector2D v) {
		setPosition(v);
	}

	public void setEntityView(EntityView v) {
		removeComponent(view);
		this.view = v;
		addComponent(view);
	}

	public EntityView getEntityView() {
		return view;
	}

	/**
	 * Sets the current {@link CollisionModel} of this entity
	 * 
	 * @param m
	 *            the new CollisionModel
	 * 
	 * @see ViewBasedCollisionModel
	 * @see BasicCollisionModel
	 * @see CenterPointCollisionModel
	 */
	public void setCollisionModel(CollisionModel m) {
		removeComponent(collisionModel);
		this.collisionModel = m;
		addComponent(m);
	}

	/**
	 * Returns the current {@link CollisionModel} of this entity
	 * 
	 * @return the current CollisionModel
	 * 
	 * @see ViewBasedCollisionModel
	 * @see BasicCollisionModel
	 * @see CenterPointCollisionModel
	 */
	public CollisionModel getCollisionModel() {
		return collisionModel;
	}

	public Level getLevel() {
		if (level == null) {
			throw new SpecuspaceException("Entity not spawned!");
		}
		return level;
	}

	/**
	 * Initializes this entity with the given properties. <br>
	 * The influence of properties may vary for different subclasses.<br>
	 * Should be overwritten by every inheriting entity.
	 * 
	 * @param p
	 *            Entity properties
	 */
	public void init(Properties p) {
		float x, y;
		String spawnX = (String) p.get("spawnx");
		String spawnY = (String) p.get("spawny");
		if (spawnX != null && !spawnX.isEmpty()) {
			x = Float.valueOf(spawnX);
		} else {
			x = 0.0f;
		}
		if (spawnY != null && !spawnY.isEmpty()) {
			y = Float.valueOf(spawnY);
		} else {
			y = 0.0f;
		}
		setPosition(new Vector2D(x, y));
	}

}
